#include <stdio.h>
#include "uart.h"
#include "board.h"
#include "clock.h"
//#include "device/sbarmsoc_sb3500.h"

#define CONFIG_PL011_SERIAL_RLCR

struct pl01x_regs
{
    unsigned long dr;               /* 0x00 Data register */
    unsigned long ecr;              /* 0x04 Error clear register (Write) */
    unsigned long pl010_lcrh;       /* 0x08 Line control register, high byte */
    unsigned long pl010_lcrm;       /* 0x0C Line control register, middle byte */
    unsigned long pl010_lcrl;       /* 0x10 Line control register, low byte */
    unsigned long pl010_cr;         /* 0x14 Control register */
    unsigned long fr;               /* 0x18 Flag register (Read only) */
#ifdef CONFIG_PL011_SERIAL_RLCR
    unsigned long pl011_rlcr;       /* 0x1c Receive line control register */
#else
    unsigned long reserved;
#endif
    unsigned long ilpr;             /* 0x20 IrDA low-power counter register */
    unsigned long pl011_ibrd;       /* 0x24 Integer baud rate register */
    unsigned long pl011_fbrd;       /* 0x28 Fractional baud rate register */
    unsigned long pl011_lcrh;       /* 0x2C Line control register */
    unsigned long pl011_cr;         /* 0x30 Control register */
};

#define UART_PL01x_RSR_OE               0x08
#define UART_PL01x_RSR_BE               0x04
#define UART_PL01x_RSR_PE               0x02
#define UART_PL01x_RSR_FE               0x01

#define UART_PL01x_FR_TXFE              0x80
#define UART_PL01x_FR_RXFF              0x40
#define UART_PL01x_FR_TXFF              0x20
#define UART_PL01x_FR_RXFE              0x10
#define UART_PL01x_FR_BUSY              0x08
#define UART_PL01x_FR_TMSK              (UART_PL01x_FR_TXFF + UART_PL01x_FR_BUSY)

/*
 *  PL011 definitions
 *
 */
#define UART_PL011_LCRH_SPS             (1 << 7)
#define UART_PL011_LCRH_WLEN_8          (3 << 5)
#define UART_PL011_LCRH_WLEN_7          (2 << 5)
#define UART_PL011_LCRH_WLEN_6          (1 << 5)
#define UART_PL011_LCRH_WLEN_5          (0 << 5)
#define UART_PL011_LCRH_FEN             (1 << 4)
#define UART_PL011_LCRH_STP2            (1 << 3)
#define UART_PL011_LCRH_EPS             (1 << 2)
#define UART_PL011_LCRH_PEN             (1 << 1)
#define UART_PL011_LCRH_BRK             (1 << 0)

#define UART_PL011_CR_CTSEN             (1 << 15)
#define UART_PL011_CR_RTSEN             (1 << 14)
#define UART_PL011_CR_OUT2              (1 << 13)
#define UART_PL011_CR_OUT1              (1 << 12)
#define UART_PL011_CR_RTS               (1 << 11)
#define UART_PL011_CR_DTR               (1 << 10)
#define UART_PL011_CR_RXE               (1 << 9)
#define UART_PL011_CR_TXE               (1 << 8)
#define UART_PL011_CR_LPE               (1 << 7)
#define UART_PL011_CR_IIRLP             (1 << 2)
#define UART_PL011_CR_SIREN             (1 << 1)
#define UART_PL011_CR_UARTEN            (1 << 0)

#define UART_PL011_IMSC_OEIM            (1 << 10)
#define UART_PL011_IMSC_BEIM            (1 << 9)
#define UART_PL011_IMSC_PEIM            (1 << 8)
#define UART_PL011_IMSC_FEIM            (1 << 7)
#define UART_PL011_IMSC_RTIM            (1 << 6)
#define UART_PL011_IMSC_TXIM            (1 << 5)
#define UART_PL011_IMSC_RXIM            (1 << 4)
#define UART_PL011_IMSC_DSRMIM          (1 << 3)
#define UART_PL011_IMSC_DCDMIM          (1 << 2)
#define UART_PL011_IMSC_CTSMIM          (1 << 1)
#define UART_PL011_IMSC_RIMIM           (1 << 0)

#define EAGAIN   11 /* Try again */


static struct pl01x_regs *uart_base = (struct pl01x_regs *)UART_BASE;
static struct pl01x_regs *serial_base = (struct pl01x_regs *)SERIAL_BASE;

static int pl01x_putc(struct pl01x_regs *regs, char c)
{
    /* Wait until there is space in the FIFO */
    if (readl(&regs->fr) & UART_PL01x_FR_TXFF)
        return -EAGAIN;

    /* Send the character */
    writel(c, &regs->dr);

    return 0;
}

static int pl01x_getc(struct pl01x_regs *regs)
{
    unsigned int data;

    /* Wait until there is data in the FIFO */
    while (readl(&regs->fr) & UART_PL01x_FR_RXFE);
        //return -EAGAIN;

    data = readl(&regs->dr);

    /* Check for an error flag */
    if (data & 0xFFFFFF00) {
        /* Clear the error */
        writel(0xFFFFFFFF, &regs->ecr);
        return -1;
    }

    return (int) data;
}

static int pl01x_tstc(struct pl01x_regs *regs)
{
    return !(readl(&regs->fr) & UART_PL01x_FR_RXFE);
}

static int pl011_generic_serial_init(struct pl01x_regs *regs)
{
    /* disable everything */
    writel(0, &regs->pl011_cr);

    return 0;
}

static int pl011_set_line_control(struct pl01x_regs *regs)
{
    unsigned int lcr;
    /*
     * Internal update of baud rate register require line
     * control register write
     */
    lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN;
    writel(lcr, &regs->pl011_lcrh);
    return 0;
}

static int pl011_generic_setbrg(struct pl01x_regs *regs,
                int clock, int baudrate)
{
    unsigned int temp;
    unsigned int divider;
    unsigned int remainder;
    unsigned int fraction;

    /*
     * Set baud rate
     *
     * IBRD = UART_CLK / (16 * BAUD_RATE)
     * FBRD = RND((64 * MOD(UART_CLK,(16 * BAUD_RATE)))
     *       / (16 * BAUD_RATE))
     */
    temp = 16 * baudrate;
    divider = clock / temp;
    remainder = clock % temp;
    temp = (8 * remainder) / baudrate;
    fraction = (temp >> 1) + (temp & 1);

    writel(divider, &regs->pl011_ibrd);
    writel(fraction, &regs->pl011_fbrd);

    pl011_set_line_control(regs);
    /* Finally, enable the UART */
    writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE |
        UART_PL011_CR_RXE | UART_PL011_CR_RTS, &regs->pl011_cr);

    return 0;
}

/* -------------------------------------------------------------------- */

int uart_init(int baudrate)
{
	unsigned long val;

	val = readl(DC_REG(DC_PAD_CTRL));
	val |= 1 << 2;
	writel(val, DC_REG(DC_PAD_CTRL));

	/* ENABLE UART2 */
	val = readl(DC_REG(DC_AMBA_RST));
	val |= (1 << 11);
	writel(val, DC_REG(DC_AMBA_RST));

	pl011_generic_serial_init(uart_base);
	pl011_generic_setbrg(uart_base, clock_get_apb(), baudrate);

	return 0;
}

void uart_putc(const char c)
{
	if (c == '\n')
			while (pl01x_putc(uart_base, '\r') == -EAGAIN);

	while (pl01x_putc(uart_base, c) == -EAGAIN);
}

void uart_puts(const char *s)
{
	while (*s)
	{
		uart_putc(*s++);
	}
}

int uart_getc(void)
{
	while (1)
	{
		int ch = pl01x_getc(uart_base);
		if (ch == -EAGAIN)
			continue;
		return ch;
	}
}

int uart_tstc(void)
{
	return pl01x_tstc(uart_base);
}

/* -------------------------------------------------------------------- */

int serial_init(int baudrate)
{
	unsigned long val;

	val = readl(DC_REG(DC_PAD_CTRL));
	val |= 1 << 9;
	writel(val, DC_REG(DC_PAD_CTRL));

	/* ENABLE UART1 */
	val = readl(DC_REG(DC_AMBA_RST));
	val |= (1 << 8);
	writel(val, DC_REG(DC_AMBA_RST));

	pl011_generic_serial_init(serial_base);
	pl011_generic_setbrg(serial_base, clock_get_apb(), baudrate);

	return 0;
}

void serial_putc(const char c)
{
	if (c == '\n')
		while (pl01x_putc(serial_base, '\r') == -EAGAIN);

	while (pl01x_putc(serial_base, c) == -EAGAIN);
}

void serial_puts(const char *s)
{
	while (*s)
	{
		serial_putc(*s++);
	}
}


void serial_puts_0(const char *s, int len)
{
	while (len--)
	{
		serial_putc(*s++);
	}
}

int serial_getc(void)
{
	while (1)
	{
		int ch = pl01x_getc(serial_base);
		if (ch == -EAGAIN)
			continue;
		return ch;
	}
}

int serial_tstc(void)
{
	return pl01x_tstc(serial_base);
}
